﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;

// 보정된 점을 Hermit 보간할 때 쓸 형식.
public struct HermitPoint
{
    // 좌표는 보정하기 전 위치에서 가져온다.
    public Vector2 point;
    public Vector2 nextPoint;
    public Vector2 prePoint;
    public bool isCurved;
}

public class ShapeCalculator : MonoBehaviour
{
	const float		c_dotTrunThreshold      = 200f;			// 이 길이만큼 떨어지지 않은 점들은 지워버린다.
    const float     c_lineThresholde        = 0.1f;
    const float     c_dotCurveThreshold     = 100f;         // 커브용 점을 저장할 간격. 
    const float     c_angleDiff             = 22.5f;

    static Vector2 c_defaultVector = new Vector2(-1, -1);

    public static void SelectStraightPointFromInput(LinkedList<Vector2> inputList, LinkedList<Vector2> selectedList)
    {
        // 맨 처음 점 보관
        Vector2 firstPoint = inputList.First.Value;
        Vector2 refPoint = firstPoint;

        int numberOfLine = 0;

        selectedList.AddLast(firstPoint);

        if (inputList.Count < 2)
        {
            return;
        }
 
        // 점이 2개 이상이므로 탐색한다.
        int startCount = inputList.Count;

        LinkedListNode<Vector2> tempNode = inputList.First;
        Vector2 lastLineStart = firstPoint;
        Vector2 lastLineEnd = new Vector2(-1f, -1f);        // 텍스쳐 좌표계는 0부터 시작함.

        float distanceFromStart = 0f;		// Threshold 에 도달하기까지 점들의 거리 합
        float distanceFromEnd = 0f;

        // 각도 판별에 쓰일 축.
		Quaternion axisQt = Quaternion.LookRotation(Vector3.right, Vector3.forward);
        float referenceAngle = -9999f;	// 첫 루프에서 무조건 덮어씌울 수 있게 설정함


        // 점을 순차로 탐색하면서, 길이가 Threshold 간격 단위로 점을 저장
        while (tempNode.Next != null)
        {
            tempNode = tempNode.Next;
            distanceFromStart += Vector2.Distance(tempNode.Value, refPoint);	// 이전 점과의 거리 저장
            Vector2 referenceLine;

            if (distanceFromStart > c_dotTrunThreshold)
            {
                if (lastLineEnd == c_defaultVector)
                {
                    // 끝점이 저장돼있지 않으면 우선 현재 점을 저장해둔다.
                    lastLineEnd = tempNode.Value;
                    referenceLine = lastLineEnd - lastLineStart;
                    Quaternion referenceQt = Quaternion.LookRotation(referenceLine, Vector3.forward);
                    referenceAngle = Quaternion.Angle(axisQt, referenceQt);
                }
                else
                {
                    // 길이가 Threshold 이상이므로 현재 점이 꺾인점인지 판별.
                    distanceFromEnd = Vector2.Distance(tempNode.Value, lastLineEnd);
                    if (distanceFromEnd > c_lineThresholde)
                    {
                        Vector2 segmentLine = tempNode.Value - lastLineEnd;
						Quaternion segmentQuat = Quaternion.LookRotation(segmentLine, Vector3.forward);
                        float angle = Quaternion.Angle(axisQt, segmentQuat);

                        if (Mathf.Abs(angle - referenceAngle) >22.5f)
                        {
                            // 이 점의 직전 점은 꺾인점이다. selectedList 에 lineEnd를 넣고, lineStart를 lineEnd로 바꾸고, numberOfLine 을 증가시킨다.
                            Debug.Log("Angle : " + angle + " reference angle : " + referenceAngle + "euler axis : " + axisQt.eulerAngles + " euler segment : " + segmentQuat.eulerAngles);
                            selectedList.AddLast(lastLineEnd);

                            lastLineStart = lastLineEnd;
                            lastLineEnd = c_defaultVector;

                            distanceFromStart = 0;
                            distanceFromEnd = 0;
                            ++numberOfLine;
                        }
                        else
                        {
                            // 이 점의 직전 점은 꺾인점이 아니므로 lineEnd 를 직전의 점으로 대체한다.
                            lastLineEnd = tempNode.Previous.Value;
                            referenceLine = lastLineEnd - lastLineStart;
                            Quaternion referenceQt = Quaternion.LookRotation(referenceLine, Vector3.forward);
                            referenceAngle = Quaternion.Angle(axisQt, referenceQt);
                        }
                    }
                }
            }
            refPoint = tempNode.Value;
        }

        // 마지막 점은 강제로 대입
        selectedList.AddLast(tempNode.Value);

        // 마지막 점과 첫 점이 가까우면 첫 점을 대입
        if (Vector2.Distance(firstPoint, tempNode.Value) < c_dotTrunThreshold)
        {
            selectedList.AddLast(firstPoint);
        }

        // Debug.Log("Last Point : " + tempNode.Value);
        Debug.Log("selectedList Count : " + selectedList.Count);
    }

    public static void SelectHermitPointFromInput(LinkedList<Vector2> inputList, LinkedList<HermitPoint> selectedList)
    {
        Vector2 firstPoint = inputList.First.Value;		// 맨 처음 점 보관 (리스트 맨 처음과 나중에 집어넣음)

        int inputCount = inputList.Count;

        Vector2[] inputArray = new Vector2[inputCount];
        inputList.CopyTo(inputArray, 0);

        Vector2 refPoint = firstPoint;

        //루프를 돌기 전에 첫번째 점은 먼저 저장하고 시작한다.
        HermitPoint point = new HermitPoint();
        point.point = inputArray[0];
        point.prePoint = inputArray[0];
        //selectedList.Add(point);		// nextPoint는 현재 없으므로 나중에

        if (inputList.Count < 2)
        {
            Debug.Log("return");
            return;
        }

        float distSum = 0f;		// Threshold 에 도달하기까지 점들의 거리 합
        int lastPointIndexFromInput = 0;
        int pointInterval = 0;

        int interval = 0;
        Vector2 lastNext;
        Vector2 pointPre;
        for (int i = 1; i < inputCount; ++i) 
        {
            distSum += Vector2.Distance(inputArray[i], inputArray[i-1]);
            ++pointInterval;

            if (distSum > c_dotTrunThreshold || i == inputCount - 1)
            {
                interval = (pointInterval)/4;
                lastNext = inputArray[lastPointIndexFromInput + interval];
                pointPre = inputArray[lastPointIndexFromInput + interval * 3];

                // 부드러운 곡선을 위해 lastNext 는 point.point 와 point.pre 와 직선상에 존재해야 함.
                float distance = Vector2.Distance(point.point, lastNext);
                Vector2 nextPoint = point.point + ((point.point - point.prePoint).normalized * distance);

                point.nextPoint = nextPoint;
                selectedList.AddLast(point);

                point = new HermitPoint();
                point.point = inputArray[i];
                point.prePoint = pointPre;

                distSum = 0f;
                pointInterval = 0;
                lastPointIndexFromInput = i;
            }
        }

		if (selectedList.Count < 1)
		{
			Debug.Log("return");
			return;
		}

		// 리스트에 넣을 첫번째 점을 생성한다.
		var firstHermitP		= new HermitPoint();
		firstHermitP.point		= inputArray[0];
		firstHermitP.prePoint 	= inputArray[0];
		firstHermitP.nextPoint	= selectedList.First.Value.point;	// 대충 다음점 넣기... (잘못된것)
		selectedList.AddFirst(firstHermitP);

        // 마지막 점은 강제로 대입
        interval = (pointInterval) / 4;
        lastNext = inputArray[lastPointIndexFromInput + interval];
        pointPre = inputArray[lastPointIndexFromInput + (3 * interval)];

        point.nextPoint = lastNext;
        selectedList.AddLast(point);

        Debug.Log("Original Point : " + inputCount + " Selected Point : " + selectedList.Count);
    }

    public static void CorrectionFromSelected(LinkedList<Vector2> correctionList, LinkedList<Vector2> selectedList)
    {
        int count = selectedList.Count;

        Vector2[] selectedArray = new Vector2[count];
        selectedList.CopyTo(selectedArray, 0);
        
        int[] directionArray = new int[count];

        //첫번째 점을 대입
        correctionList.AddLast(selectedArray[0]);
        directionArray[0] = -1;

        Quaternion axisQuat = Quaternion.LookRotation(Vector3.right, Vector3.forward);

        // i-1 번째 점과 i번째 점의 direction 에 따라 보정
        for (int i = 1; i < selectedList.Count ; ++i)
        {
            Quaternion quaternion = Quaternion.LookRotation(selectedArray[i] - selectedArray[i - 1], Vector3.forward);
            float angle = Quaternion.Angle(axisQuat, quaternion);
            int direction = DirectionFromAngle(angle);

            if (angle < 10f || angle > 350f || (angle > 170f && angle < 190f))
            {
                correctionList.AddLast(new Vector2(selectedArray[i].x, selectedArray[i - 1].y));
            }
            else if ((angle > 80f && angle < 100f) || (angle > 260f && angle < 280f))
            {
                correctionList.AddLast(new Vector2(selectedArray[i - 1].x, selectedArray[i].y));
            }
            else
            {
                correctionList.AddLast(selectedArray[i]);
            }
            directionArray[i] = direction;
        }

        correctionList.AddLast(selectedArray[count - 1]);
    }

    private static int DirectionFromAngle(float angle)
    {
        int direction = (int)((angle + 22.5f) / 45f);
        return direction;
    }

}
